/*
* File: http_parser.c
* Version: 1.0
* Last modified on Mon Mars 10 2014 by group 5
* Members:
* Johan Forsblom, Johanna Carlsson, Dragana Todorovic,
* Hampus Herbertsson, Robin Henriksson
* -----------------------------------------------------
* This file implements the http_parser.h interface.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#include <process.h>
#include "http_parser.h"
#include "logfile.h"
#include "webserver.h"
#include "configFile.h"


/* Constants */
#define BUFFER_SIZE		512
#define MIME_GIF_IMAGE  "Content-Type:image/gif\r\n"
#define MIME_JPG_IMAGE  "Content-Type:image/jpeg\r\n"
#define MIME_PNG_IMAGE  "Content-Type:image/png\r\n"
#define MIME_ZIP_FILE   "Content-Type:application/zip\r\n"
#define MIME_HTML_TEXT  "Content-Type:text/html\r\n"
#define MIME_FAVICON    "Content-Type:image/x-icon\r\n"
#define MIME_OTHERS	    "Content-Type:text/plain\r\n"

#define HTTP_200_OK                 "HTTP/1.1 200 OK\r\n"
#define HTTP_ERROR_404_NOT_FOUND	"HTTP/1.1 404 Not Found\r\nContent-Type:text/html\r\n\r\n"
#define HTTP_304_NOT_MODIFIED		"\n\rHTTP/1.1 304 Not Modified\r\n"
#define HTTP_ERROR_404_ERRORPAGE	"<html><body><h1>ERROR:404, FILE NOT FOUND</h1><br></body></html>"
#define HTTP_CLOSE_CONNECTION	    "Connection: close\r\n"
#define SERVER_NAME                 "Server: T3H L33T S3RV3R\r\n"

#define IF_MODIFIED_SINCE_STRLEN        19
#define IF_MODIFIED_SINCE_DATE_LENGTH   29

#define GMT 0 

#define DEBUG_MODE

/* Static variables */
static  logFileT logFile;

/* structure used for converting local date to HTTP date */
typedef struct {
	int date;
	int month;
	int year;
	char timeStamp[10];
}HTTP_dateT;

/* Function Prototypes */
static int getFileSize(FILE *file);
static char *convertMonth(int month);
static char *convertWeekday(int day);
static int getWeekday(int y,int m,int d);
static void getServerDateAndTime(char *dateTimeString);
static void getFileDate(char *filename,char *httpDate);
static void getMimeType(char *filename,char *outBuffer);
static int findString(char *str, char *text, unsigned int start);
static void sendFile(FILE *file,char *outBuffer,int clientSocket);
static void sendHttpResponse(FILE *file,char *filename,char *outBuffer,int clientSocket);
static int checkLastModfiedDate(FILE *file,char *filename,char *webBrowserModifiedDate,int clientSocket);


/* Function: getWeekday
* weekday = getWeekday(year, month, day)
* --------------------------------------
* This function returns the weekday of the
* given year, month and day. All in one line!
* Pretty neat! (for us nerds) =;)
*
* This One Liner Solution for "C" was given by Michael Keith and Tom Craver
*/
static int getWeekday(int y,int m,int d)
{
	return (d+=m<3?y--:y-2,23*m/9+d+4+y/4-y/100+y/400)%7;
}


/* Function: getFileSize
* weekday = getFileSize(file)
* --------------------------------------
* This function returns the filesize of the
* given file.
*/
static int getFileSize(FILE *file)
{
	int size=0;
	fseek(file, 0L, SEEK_END);
	size = ftell(file);
	fseek(file, 0L, SEEK_SET);
	return size;
}

/* Function: getServerDateAndTime
* weekday = getServerDateAndTime(dateTimeString)
* --------------------------------------
* This function returns the server date and
* time (GMT).
*/
static void getServerDateAndTime(char *dateTimeString)
{
	int date;
	int year;
	char day[10];
	char month[10];
	int hours=0;
    int minutes=0;
    int seconds=0;
	char timeString[40];
	time_t currentTime;
    struct tm *gmtTime;

    // Get current time.
	currentTime = time(NULL);

    // Get GMT time
    gmtTime = gmtime ( &currentTime );

	// Convert time to local time
	sprintf(timeString,"%s",ctime(&currentTime));

	// get all the parts of the date
	sscanf(timeString,"%s %s %d %d:%d:%d %d",day,month,&date,&hours,&minutes,&seconds,&year);

    // convert local time (hours) to GMT tim
    hours = ((gmtTime->tm_hour+GMT)%24);

	// convert it to HTTP date structure
	sprintf(dateTimeString,"%s, %d %s %d %d:%d:%d GMT\r\n",day,date,month,year,hours,minutes,seconds);
}

/* Function: findString
* result = findString(str, text, start)
* --------------------------------
* This function looks for the string in the given text.
* It starts looking at the given 'start" position,
* if found, it returns the position where it
* occured. If one of the input strings is NULL
* or if the string wasn't found, it returns -1
*/
static int findString(char *str, char *text, unsigned int start)
{
	char *cptr;

	if (str == NULL) return -1;
	if (text == NULL) return -1;
	if (start < 0) start = 0;
	if (start > strlen(text)) return (-1);
	cptr = strstr(text + start, str);
	if (cptr == NULL) return (-1);
	return ((int) (cptr - text));
}

/* Function: convertMonth
* monthString = convertMonth(month)
* -----------------------------
* This function takes which month it is in
* digital format, and returns the month as a string
*/
static char *convertMonth(int month)
{
	if(month==1)
		return("Jan");
	if(month==2)
		return("Feb");
	if(month==3)
		return("Mar");
	if(month==4)
		return("Apr");
	if(month==5)
		return("May");
	if(month==6)
		return("Jun");
	if(month==7)
		return("Jul");
	if(month==8)
		return("Aug");
	if(month==9)
		return("Sep");
	if(month==10)
		return("Oct");
	if(month==11)
		return("Nov");
	if(month==12)
		return("Dec");

	return 0;
}

/* Function: convertWeekday
* weekday = convertWeekday(day)
* -----------------------------
* This function takes which day it is in
* digital format, and returns the day as a string
*/
static char *convertWeekday(int day)
{
	if(day==0)
		return "Sun";
	if(day==1)
		return "Mon";
	if(day==2)
		return "Tue";
	if(day==3)
		return "Wed";
	if(day==4)
		return "Thu";
	if(day==5)
		return "Fri";
	if(day==6)
		return "Sat";

	return 0;
}

/* Function: getFileDate
* getFileDate(filename, httpDate)
* ----------------------------------------------------------------
* This function gets the date of the given file, and returns it
* to the httpDate pointer.
*/
/* Function: getFileDate
* getFileDate(filename, httpDate)
* ----------------------------------------------------------------
* This function gets the date of the given file, and returns it
* to the httpDate pointer.
*/
static void getFileDate(char *filename,char *httpDate)
{
	char timeStr[100] = "";
	struct stat buf;
	char month[4] = "";
	char dateString[4];
	char weekdayString[20]="";
    int hours;
    int minutes;
    int seconds;
	char outBuffer[BUFFER_SIZE]= "";
	HTTP_dateT date;
    time_t currentTime;
    struct tm *gmtTime;

    // Get current time.
	currentTime = time(NULL);

    // Get GMT time
    gmtTime = gmtime ( &currentTime );

	if (!stat(filename, &buf)) {
		// Get local time
		strftime(timeStr, 100, "%d-%m-%Y %H:%M:%S", localtime( &buf.st_mtime));

		// convert to HTTP date structure
		sscanf(timeStr,"%d-%d-%d %d:%d:%d",&date.date,&date.month,&date.year,&hours,&minutes,&seconds);

		if(date.date<10){
			sprintf(dateString,"0%d",date.date);
		}
		else{
			sprintf(dateString,"%d",date.date);
		}

		// Convert month to string format
		sprintf(month,"%s",convertMonth(date.month));

        // Convert hours to GMT
        hours = ((gmtTime->tm_hour+GMT)%24);

		// get weekday name of the date.
		sprintf(weekdayString,"%s",convertWeekday(getWeekday(date.year,date.month,date.date)));

		// Create the final http Date
		sprintf(httpDate,"%s, %s %s %d %d:%d:%d GMT",weekdayString,dateString,month,date.year,hours,minutes,seconds);
	}
}

/* Function: checkLastModfiedDate
* changed = checkLastModfiedDate(file, filename, webBrowserModifiedDate)
* ----------------------------------------------------------------------
* This function compares the given file date with the
* last modified date in GET. Returns 1 if they are different,
* 0 if they are the same.
*/
static int checkLastModfiedDate(FILE *file,char *filename,char *webBrowserModifiedDate,int clientSocket)
{
	char outBuffer[BUFFER_SIZE]= "";
	char httpDate[40];
	int fileSizeInt;

	fileSizeInt = getFileSize(file);							// get size of the file to be sent
	getLogfileSize(&logFile,fileSizeInt);						// get file size for log file
	getFileDate(filename,httpDate);

	//if dates are equal
	if(strcmp(httpDate,webBrowserModifiedDate)==0) {
		getLogResponse(&logFile,"HTTP/1.1\" 304");
		//send code 304
		strcpy(outBuffer,HTTP_304_NOT_MODIFIED);
		send(clientSocket, outBuffer, strlen(outBuffer), 0);

		//send file date
		sprintf(outBuffer,"Last-Modified: %s", httpDate);
		send(clientSocket,outBuffer, strlen(outBuffer),0);

		#ifdef DEBUG_MODE
			printf("Dates are equal, Not sending webpage\n");
		#endif

		return 0;
	}
	else{
		#ifdef DEBUG_MODE
			printf("dates differ, sending update.\n");
		#endif
		return 1;
	}
	return 0;
}

/* Function: getMimeType
* getMimeType(filename,outBuffer)
* ----------------------------------------------------------------
* This function checks the MIME type of the file and
* adds it to the outbuffer
*/
static void getMimeType(char *filename,char *outBuffer)
{
	printf("Sending file '%s' \n",filename);

	//check item filetype
	if (strstr(filename, ".gif") != NULL)
		strcat(outBuffer, MIME_GIF_IMAGE);

	else if(strstr(filename, ".ico") != NULL)
		strcat(outBuffer, MIME_FAVICON);

	else if(strstr(filename, ".jpg") != NULL)
		strcat(outBuffer, MIME_JPG_IMAGE);

	else if(strstr(filename, ".png") != NULL)
		strcat(outBuffer, MIME_PNG_IMAGE);

	else if(strstr(filename, ".zip") != NULL)
		strcat(outBuffer, MIME_ZIP_FILE);

	else if(strstr(filename, ".html")!=NULL)
		strcat(outBuffer, MIME_HTML_TEXT);
	else
		strcat(outBuffer, MIME_OTHERS);
}

/* Function: sendFile
* sendFile(file,outBuffer,clientSocket)
* ----------------------------------------------------
* This function sends the given file to the webbrowser
*/
static void sendFile(FILE *file,char *outBuffer,int clientSocket)
{
	int bufferLength;   // Buffer length for readling file

	//clear outBuffer, so we don't send garbage!
	memset(outBuffer,0,BUFFER_SIZE);

	//while we're not at the end of the file
	while(!feof(file) )
	{
		//read and send a part of the file
		bufferLength = fread(outBuffer,1,BUFFER_SIZE,file);
		send(clientSocket,outBuffer,BUFFER_SIZE,0);

		//clear the send bufer
		memset(outBuffer,0,BUFFER_SIZE);
	}
}

/* Function: sendHttpResponse
* usage: sendHttpResponse(FILE,filename,outBuffer,clientsocket)
* -------------------------------------------------------------
* This function sends the proper HTTP response to the WebBrowser.
*/
static void sendHttpResponse(FILE *file,char *filename,char *outBuffer,int clientSocket)
{
	char fileDate[60];
	char serverDate[50];
	char fileDateTmp[40];
	char fileSizeStr[30];
	char serverDateTmp[50];
	int fileSizeInt;

	fileSizeInt = getFileSize(file);							//get size of the file to be sent
	getLogfileSize(&logFile,fileSizeInt);						// get file size for log file
	sprintf(fileSizeStr,"Content-Length: %d\r\n",fileSizeInt);  //create the "Content-length:" string
	getServerDateAndTime(serverDateTmp);                        //get server Date and Time
	getLogTimeStamp(&logFile,serverDateTmp);					// get server-time for log file
	sprintf(serverDate,"Date: %s",serverDateTmp);
	getFileDate(filename,fileDateTmp);                          //get file date.
	sprintf(fileDate,"Last-Modified: %s\r\n",fileDateTmp);      //complete the fileDate return message "Last-Modified: <date>

	memset(outBuffer,0,BUFFER_SIZE);			//clear the send bufer
	getLogResponse(&logFile,"HTTP/1.1\" 200");

	strcat(outBuffer,HTTP_200_OK);              //write HTTP/1.1 200 OK
	strcat(outBuffer,SERVER_NAME);              //write in server name
	strcat(outBuffer,serverDate);               //write in Date:
    strcat(outBuffer,"\r\n");                   //add a newline, since serverDate dont have one at the end
	getMimeType(filename,outBuffer);            //write in Content-Type:
	strcat(outBuffer,fileSizeStr);              //write in Content-Length:
	strcat(outBuffer,fileDate);                 //write in Last-Modified:
	strcat(outBuffer,HTTP_CLOSE_CONNECTION);    //write in Connection:close
	strcat(outBuffer,"\r\n");                   //write in the extra "\r\n" to mark end of HTTP response

	printf("%s", outBuffer);
	send(clientSocket,outBuffer, strlen(outBuffer),0);
}


/* Function: parseHTTP
* usage: use with _beginthread() like this:
* if (_beginthread(parseHTTP, BUFFER_SIZE, (void *)parData) < 0)
* ----------------------------------------------------------------
* This function parses the web browser request. The given argument
* is a parDataT struct.
*/
void parseHTTP(void *argument)
{
	int clientSocket;				// the client socket
	int returnCode;					// Return code
	FILE *file;						// Filepointer
	char inBuffer[BUFFER_SIZE];		// input buffer to handle GET request
	char command[BUFFER_SIZE];		// Command buffer
	char outBuffer[BUFFER_SIZE];	// The outbuffer HTTP message we send back
	char filename[BUFFER_SIZE];		// File name buffer
	char lastModifiedDate[50];      // the last modified date
	int lastModifiedPosition;		// where in the GET string we find "If-Modified-Since"
    char browserReq[15000]="";      // the browser request     
	char serverTimeStamp[50];
	char hostName[100];
	char homePageFolder[255];
	char fileNameTmp[BUFFER_SIZE];
	char filePath[BUFFER_SIZE];
	parDataT *parData = (parDataT*)argument;

	clearLogStruct(&logFile);
	//clear the send buffer
	memset(outBuffer,0,BUFFER_SIZE);
    memset(inBuffer,0,BUFFER_SIZE);
	// First we set the clientSocket to argument
	clientSocket = parData->clientSocket;
	sprintf(hostName, "%s", parData->hostName);
	free(argument);
	getLogConnectingClient(&logFile,hostName);

    do
    {
	    // Receive the command request from web browser
	    returnCode = recv(clientSocket, inBuffer, BUFFER_SIZE, 0);

        if(returnCode>0)
        {
            strcat(browserReq,inBuffer);
        }
       
    }while(returnCode==BUFFER_SIZE);

	// If we could not reieve the data
	if (returnCode <= 0) {
		printf("ERROR:Receive failed, or buffer was empty\n");
		closesocket(clientSocket);
		printf("closing down thread.\n");
		_endthread();
        return;
	}

	#ifdef DEBUG_MODE

        if(strlen(browserReq)>0){
		    //Print the incoming data
		    printf("\n\nPARSING:\n\n%sEND OF PARSE\n\n",browserReq);
        }
	#endif



	// Cut out command and filename
	sscanf(browserReq, "%s %s \n", command, fileNameTmp);
	getHomePageFolder(homePageFolder);
	strcpy(filename, &fileNameTmp[1]);

    // if the homepage folder path isn't a /
	if(homePageFolder[0]!='/'){

        // create the full path to the file
		sprintf(filePath, "%s%s", homePageFolder, filename);
        sprintf(filename,"%s",filePath);
	}

	getLogCommandAndURI(&logFile,command, filename);

	// Check if command really is GET, if not, we close down the thread.
	if (strcmp(command, "GET") != 0){
		closesocket(clientSocket);
		printf("closing down thread.\n");
		_endthread();
        return;
	}

	// We start looking at the second char to get rid of the slash "/" in the beginning.
	file = fopen(filename,"rb");

	// If file does not exist
	if(file==NULL) {
		#ifdef DEBUG_MODE
			printf("Could not find: %s, reporting 404 file not found. \n", filename);
		#endif

		getLogResponse(&logFile,"HTTP/1.1\" 404");
		//send not found
		strcat(outBuffer,HTTP_ERROR_404_NOT_FOUND );
		send(clientSocket, outBuffer, strlen(outBuffer), 0);

		//send the 404 Webpage
		strcpy(outBuffer, HTTP_ERROR_404_ERRORPAGE);
		send(clientSocket, outBuffer, strlen(outBuffer), 0);
		getServerDateAndTime(serverTimeStamp);
		getLogTimeStamp(&logFile, serverTimeStamp);
		//close socket and thread.
		writeLogFile(&logFile);
		closesocket(clientSocket);
		printf("closing down thread.\n");
		_endthread();
        return;
	}

	//see if the last modified string exist in the GET string
	lastModifiedPosition = findString("If-Modified-Since:",browserReq,0);

	//if it is a conditional GET
	if(lastModifiedPosition!=-1) {
		//get the last modified date from the GET string
		strncpy(lastModifiedDate, browserReq + lastModifiedPosition + IF_MODIFIED_SINCE_STRLEN, IF_MODIFIED_SINCE_DATE_LENGTH);
		lastModifiedDate[IF_MODIFIED_SINCE_DATE_LENGTH] = '\0';

		//if the dates match
		if(!checkLastModfiedDate(file,filename,lastModifiedDate,clientSocket)) {
			//close down
			getServerDateAndTime(serverTimeStamp);
			getLogTimeStamp(&logFile,serverTimeStamp);
			writeLogFile(&logFile);
			printf("closing down thread.\n");
			closesocket(clientSocket);
			fclose(file);
			_endthread();
            return;
		}
	}

	//send the HTTP response
	sendHttpResponse(file,filename,outBuffer,clientSocket);

	//send the file
	sendFile(file,outBuffer,clientSocket);

	printf("closing down thread.\n");
	writeLogFile(&logFile);

	// Close down.
	fclose(file);
	closesocket(clientSocket);
	_endthread();
}
